#include <QUrl>
#include <QHostAddress>
#include "accept_link_manager.h"
#include "file_manager.h"
#include "escal.h"
#include <iostream>
#include <sstream>
#include "escal_settings.h"
using namespace Escal;
using namespace std;

AcceptLinkManager::AcceptLinkManager(FileManager *fileManager,
		const QUrl &url, QObject *parent)
	:BaseLinkManager(parent), fileManager(fileManager), url(url) {
	urlByteArray = url.toEncoded();
	pieceIndex = 0;
	offset = 0;
	buf_size = 0;
	retry = 0;
	gotPiece = false;
	connecting_to_peer = false;
	
	netHandlers[EET_RESPOND_REGISTER_USER] = 
			&AcceptLinkManager::onRespondRegisterUser;
	netHandlers[EET_RESPOND_REGISTER_URL] =
			&AcceptLinkManager::onRespondRegisterUrl;
	netHandlers[EET_RESPOND_FRIEND] = 
			&AcceptLinkManager::onRespondFriend;
	netHandlers[EET_RESPOND_DOWNLOAD_URL] = 
			&AcceptLinkManager::onRespondDownloadUrl;
	netHandlers[EET_RESPOND_FILE_INFO] = 
			&AcceptLinkManager::onRespondFileInfo;
	netHandlers[EET_RESPOND_DOWNLOAD_SECTION] =
			&AcceptLinkManager::onRespondDownloadSection;
};

AcceptLinkManager::~AcceptLinkManager() {
	stop();
	wait();
};

void AcceptLinkManager::stop() {
	quit();
	cleanUp();
};

void AcceptLinkManager::cleanUp() {
	if (gotPiece)
	{
		fileManager->releasePiece(pieceIndex);
	}
};

void AcceptLinkManager::run() {
	socket = new QTcpSocket();
	connect(socket, SIGNAL(connected()),
		this, SLOT(onConnect()), Qt::DirectConnection);
	connect(socket, SIGNAL(readyRead()),
		this, SLOT(onRead()), Qt::DirectConnection);
	connect(socket, SIGNAL(disconnected()),
		this, SLOT(onDisconnect()), Qt::DirectConnection);

	startConnect();
	exec();
	emit linkQuited(this, error);
};

void AcceptLinkManager::onRespondRegisterUser(void *buffer) {
	qDebug() << "AcceptLinkManager::onRespondRegisterUser";
	RespondRegisterUser *msg = (RespondRegisterUser*) buffer;
	qDebug() << "size:" << msg->size << "type:" << msg->type
			<< "state:" << msg->state
			<< "guid:" << msg->guid;
	
	if (msg->state != 0) {
		qDebug() << "closing the socket";
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), "failed to register user");
		socket->disconnectFromHost();
	}

 	if (msg->guid != settings->value("P2SP/Guid").toInt()) {
		settings->setValue("P2SP/Guid", (quint64)msg->guid);
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), QString("get user guid: %1")
			.arg(msg->guid));
	}

	RequestRegisterUrl req;
	sprintf(req.url, "%s", (const char*)url.toEncoded());
	cerr << "req.url: " << req.url << endl;
	req.makeSize();
	socket->write((char*)&req, req.size);
};

void AcceptLinkManager::onRespondRegisterUrl(void *buffer) {
	qDebug() << "AcceptLinkManager::onRespondRegisterUrl";
	RespondRegisterUrl *msg = (RespondRegisterUrl*) buffer;
	qDebug() << "size:" << msg->size << "type:"	<< msg->type << "\n"
		<< "state:" << msg->state;

	if (msg->state != 0) {
		qDebug() << "closing the socket";
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), "failed to register the url");
		socket->disconnectFromHost();
		return;
	}
	
	if (fileManager->isFinished()) {
		// nothing to do
		// emit linkQuited(this, error);
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), "the file is finished");
		quit();
		return;
	};
	RequestFriend req;
	sprintf(req.url, "%s", (const char*)url.toEncoded());
	req.makeSize();
	socket->write((char*)&req, req.size);
};

void AcceptLinkManager::onRespondFriend(void *buffer) {
	qDebug() << "AcceptLinkManager::onRespondFriend";
	RespondFriend *msg = (RespondFriend*) buffer;
	qDebug() << "size:" << msg->size << "type:" << msg->type;
	emit writeLog(textCursor,QTime::currentTime()
			.toString(), QString("get %1 peers").arg(msg->peer_count));
	int i;
	for (i = 0; i < msg->peer_count; i++)
	{
		Peer peer;
		stringstream ss;
		ss << (int)msg->peers[i].global_ip[3] << "." 
			<< (int)msg->peers[i].global_ip[2] << "."
			<< (int)msg->peers[i].global_ip[1] 
			<< "." << (int)msg->peers[i].global_ip[0];
		peer.globalIp = ss.str().c_str();
		ss.str("");
		ss << (int)msg->peers[i].local_ip[3] << "."
			<< (int)msg->peers[i].local_ip[2] << "."
			<< (int)msg->peers[i].local_ip[1] << "."
			<< (int)msg->peers[i].local_ip[0];
		peer.localIp = ss.str().c_str();
		peer.port = msg->peers[i].port;
		netPeers.enqueue(peer);
	}
	
	socket->disconnectFromHost();
};

void AcceptLinkManager::onRespondDownloadUrl(void *buffer) {
	qDebug() << "AcceptLinkManager::onRespondDownloadUrl";
	RespondDownloadUrl *msg = (RespondDownloadUrl*)buffer;
	if (msg->state != 0) {
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), "peer do not has the file");
		socket->disconnectFromHost();
		return;
	}

	if (fileManager->isNew())
	{
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), "requset file infomation");

		RequestFileInfo resq;
		socket->write((char*)&resq, resq.size);
		return;
	}
	else if(!gotPiece && ((pieceIndex = fileManager->getPiece()) <0)){
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), "no piece got, quiting...");
		qDebug() << "no piece got, quiting...";
    	error = true;
		socket->disconnectFromHost();
		return;
	}
	else{
		gotPiece = true;
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), tr("got piece: %1")
			.arg(pieceIndex));
	}

	RequestDownloadSection req;
	req.offset = pieceIndex * PieceSize + offset;
	req.length = 4000;
	socket->write((char*)&req, req.size);

};

void AcceptLinkManager::onRespondFileInfo(void *buffer) {
	qDebug() << "AcceptLinkManager::onRespondDownloadUrl";
	RespondFileInfo *msg = (RespondFileInfo*)buffer;
	if (msg->size == -1)
	{
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), "peer has not donwload the file yet");
		socket->disconnectFromHost();
	}

	if (fileManager->isNew())
	{
		fileManager->initNewFile(msg->file_size, true);
	}
	if(!gotPiece && ((pieceIndex = fileManager->getPiece()) <0)) {
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), "no piece got, quiting...");
   		error = true;
		socket->disconnectFromHost();
		return;
	}
	else{
		gotPiece = true;
		RequestDownloadSection req;
		req.offset = pieceIndex * PieceSize + offset;
		req.length = 4000;
		socket->write((char*)&req, req.size);
	}
};

void AcceptLinkManager::onRespondDownloadSection(void *buffer) {
	qDebug() << "AcceptLinkManager::onRespondDownloadSection";
	RespondDownloadSection* msg = (RespondDownloadSection*)buffer;
	if (msg->offset != quint64(pieceIndex * PieceSize + offset)) {
		emit writeLog(textCursor, QTime::currentTime().toString(),
			"msg.offset != pieceIndex * PieceSize + offset");
		socket->disconnectFromHost();
	}
	int ret;
	if((ret = fileManager->write(pieceIndex, offset, gotPiece, 
			QByteArray(msg->data, msg->length)))) {
		qDebug() << "write error:" << ret;
		socket->disconnectFromHost();	
		return;
	}
	if(!gotPiece && ((pieceIndex = fileManager->getPiece()) <0)) {
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), "no piece got, quiting...");
   		error = true;
		socket->disconnectFromHost();
		return;
	}
	else{
		gotPiece = true;
		RequestDownloadSection req;
		req.offset = pieceIndex * PieceSize + offset;
		req.length = 4000;
		socket->write((char*)&req, req.size);
	}
};

void AcceptLinkManager::onConnect() {
	qDebug() << "AcceptLinkManager::onConnect()";
	if (!connecting_to_peer) {
		RequestRegisterUser msg;
		QHostAddress addr = socket->localAddress();
		(*(quint32*)msg.local_ip) = addr.toIPv4Address();
		msg.port = settings->value("P2SP/UploadPort").toInt();
		msg.guid = settings->value("P2SP/Guid").toInt();

		socket->write((const char*)&msg, msg.size);

	} else {
		RequestDownloadUrl req;
		sprintf(req.url, "%s", (const char*)url.toEncoded());
		cerr << "req.url" << req.url << endl;
		req.makeSize();
		socket->write((char*)&req, req.size);
	}
};

void AcceptLinkManager::onDisconnect() {
	qDebug() << "AcceptLinkManager::onDisconnect()";
	if (socket->error() != QAbstractSocket::UnknownSocketError) {
		error = true;
		qDebug() << socket->errorString();
	};

	if (gotPiece)
	{
		fileManager->releasePiece(pieceIndex);
		gotPiece = false;
	}

	if (fileManager->isNew() || !fileManager->isFinished()) {
		sleep(1);
		startConnect();
	} else {
		quit();
	}
};

void AcceptLinkManager::onRead() {
	qDebug() << "AcceptLinkManager::onRead";
	int ret;
	BaseEvent * event;
	NetHandlerMap::const_iterator itr;
	if (buf_size < sizeof(BaseEvent)) {
		ret = socket->read(buffer + buf_size, sizeof(BaseEvent) - buf_size);
		if (ret < 0) {
			goto readError;
		}
		buf_size += ret;
		if (buf_size < sizeof(BaseEvent)) {
			return;
		}
	}

	event = (BaseEvent *) buffer;
	if (event->size < sizeof(BaseEvent) || event->size > MAX_EVENT_LENGTH) {
		qDebug() << "get a event of wrong size:" << event->size;
		buf_size = 0;
		return;
	}
	if (buf_size < event->size) {
		ret = socket->read(buffer + buf_size, event->size - buf_size);
		if (ret < 0) {
			goto readError;
		}
		buf_size += ret;
		if (buf_size < event->size) {
			return;
		}
	}

	itr = netHandlers.find(event->type);
	if (itr == netHandlers.end()) {
		qDebug() << "get a event of unknown type:" << event->type;
	}
	else {
		(this->*(itr.value()))(buffer); // call the handler
	}
	buf_size = 0;
	return;
		
readError:
	socket->disconnect();
	buf_size = 0;
	return;
};

void AcceptLinkManager::startConnect() {
	if (netPeers.empty()) {
		QString p2pServer;
		quint16 port;
		p2pServer = settings->value("P2SP/P2SPServer").toString();
		port = settings->value("P2SP/P2SpServerPort").toInt();
		connecting_to_peer = false;
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), "connecting to index server");

	
		socket->connectToHost(p2pServer, port);
	} else {
		qDebug() << "connecting to other peer";
	
		Peer peer = netPeers.dequeue();
		connecting_to_peer = true;
		emit writeLog(textCursor,QTime::currentTime()
			.toString(), QString("connecting to next peer %1:%2")
			.arg(peer.globalIp).arg(peer.port));

		socket->connectToHost(peer.globalIp, peer.port);
	};
};
